/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package util.ejb;

import entities.tools.Answer;
import entities.tools.FilledForm;
import entities.tools.Form;
import entities.tools.Question;
import entities.users.FormSigner;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import javax.annotation.Resource;
import javax.ejb.EJBContext;
import javax.ejb.Stateful;
import javax.faces.bean.SessionScoped;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

/**
 * Bean implements API needed to perform operations of sending the form, and
 * answering the questions in that form TODO: Transactional mechanism Bean
 * should implement the JEE Transaction mechanism for whole filling the form
 * process. Every time, the user is opening form for filling, a transaction is
 * created Every time, the user is answering question, is stored in database If
 * session timeout, or the user resign filling the form, the transaction is
 * rolled back. All answered questions, and the creation of formfilled entity is
 * also rolled back If the form is send, the transaction is committed, and all
 * of the answers, are persisted to database First version of the transaction
 * mechanism: Use JTA transaction inside the body, of the methods Second version
 * of the transaction mechanism: Use the Bean-Managed Transactions mechanism
 * TODO: Entities used by this session should be locked for the whole filling
 * form transaction TODO: I would like to reset whole bean, when transaction
 * timeout event is thrown. Is this possible? How? TODO: I would like to reset
 * whole bean, when session timeout event "wystąpił". Is this necessary? How to
 * do it? Why TODO: I would like to return to the user actualized version of his
 * been, by reference? Is this possible? Is this necessary? How to achieve it?
 *
 * @author Marek
 */
@Stateful
@SessionScoped
//TODO: toLearn3: How to use Remote interfase, and chousing directive. How this works? For what it's being used?  
public class FormFillingBean implements FormFillingBeanInterface, Serializable {

    @PersistenceContext(name = "test", unitName = "JEE-study-warPU")
    private EntityManager em;

    //TODO: toLearn3: What are differences between @Resource, and @EJB, and other simillar dependency injections?
    @Resource
    private EJBContext context;

    //TODO: Change the following entities to long id attributes. Do it in whole EntityBean. It make the EJB more secured against exceptions
    private Form form = null;
    private FilledForm filledForm = null;
    private FormSigner formSigner = null;
    private boolean transactionStarted = false;
    private HashMap<Long,Answer> answers = new HashMap<>();

    private String linkHash = null;

    public FilledForm getFilledForm() {
        return this.filledForm;
    }

    //TODO: Throw exception instead of return false!
    //Begin the filling of existing form - transaction is created 
    public boolean beginFillingForm(String linkHash) {

        if (this.transactionStarted == true) {
            if (this.linkHash != linkHash) {
                this.cancelFormFilling();
            } else {
                return true;
            }
        }

        this.linkHash = linkHash;

        this.filledForm = em.createQuery("select f from FilledForm f where f.linkHash=:linkHash", FilledForm.class).setParameter("linkHash", linkHash).getSingleResult();

        if (null == this.filledForm) {
            this.cancelFormFilling();
            System.out.println("FormFillingBean:beginFillingForm(): FilledForm registered with linkHash " + linkHash + " doesn't exist in database.");
            return false;
        }
        this.form = this.filledForm.getForm();
        this.formSigner = this.filledForm.getFormSigner();
        if (null == this.form) {
            this.cancelFormFilling();
            System.out.println("FormFillingBean:beginFillingForm(): Form, you have specified, doesn't exist in the database!");
            return false;
        }
        if (null == this.formSigner) {
            this.cancelFormFilling();
            System.out.println("FormFillingBean:beginFillingForm(): FormSigner, you have specified, doesn't exist in the database!");
            return false;
        }
        em.createQuery("delete from Answer a where a.filledForm=:filledForm").setParameter("filledForm", this.filledForm).executeUpdate();

        List<Question> questions = this.form.getQuestions();
        if (this.filledForm.getAnswers().size() != 0) {
            this.filledForm.getAnswers().clear();
            em.merge(this.filledForm);
        }
        for (Question question : questions) {
            Answer answer = new Answer();
            answer.setQuestion(question);
            answer.setFilledForm(filledForm);
            answer.setOrderNumber(question.getOrderNumber());
            this.answers.put(question.getId(), answer);
        }
        this.transactionStarted = true;
        return true;
    }

    //Begin the filling of existing form - transaction is created 
    @Override
    public boolean beginFillingForm(Form form, FormSigner formSigner) {

        /*
         if (this.transactionStarted == true) {
         return true;
         }
         this.form = em.find(Form.class, form.getId());
         this.formSigner = em.find(FormSigner.class, formSigner.getId());
         if (null == this.form) {
         this.cancelFormFilling();
         System.out.println("FormFillingBean:beginFillingForm(): Form, you have specified, doesn't exist in the database!");
         return false;
         }
         if (null == this.formSigner) {
         this.cancelFormFilling();
         System.out.println("FormFillingBean:beginFillingForm(): FormSigner, you have specified, doesn't exist in the database!");
         return false;
         }

         //TODO: create the register which will allow do the check if user is registered to this form. Implement the check in this method
         this.filledForm = new FilledForm();
         filledForm.setForm(this.form);
         filledForm.setFormSigner(this.formSigner);

         List<Question> questions = this.form.getQuestions();
         for (Question question : questions) {
         Answer answer = new Answer();
         answer.setQuestion(question);
         answer.setFilledForm(filledForm);
         answer.setOrderNumber(question.getOrderNumber());
         this.filledForm.getAnswers().add(answer);
         this.em.persist(answer);
         }

         this.filledForm.setFormSigner(formSigner);
         this.formSigner.getFilledForms().add(this.filledForm);

         this.em.persist(this.filledForm);
         this.em.merge(this.form);
         this.em.merge(this.formSigner);
         return true;
         */
        return false;
    }

    //Ansfers the existing question
    //Todo: toLearn3: Is it possible to easy inject one bean into another bean? Is this aproach having good performance for thy application? Is the server creating new bean instance for every injection, or inject existing one? 
    @Override
    public boolean answerQuestion(Question detachedQuestion, String answerValue) {
        //check if transaction was started
        //Check if the answers are correctly created
        {
            //TODO: create validator for every type of questions. Use this validators here to check if ansfers pattern match proper validator patterns
        }

        if (!this.answers.containsKey(detachedQuestion.getId())) {
            System.out.println("FormFillingBean:answerQuestion()->Question you specified, doesn't exist in the database.");
            return true;
        }
        Answer answer = this.answers.get(detachedQuestion.getId());
        answer.setValue(answerValue);
        this.answers.put(detachedQuestion.getId(), answer);
        return true;

    }

    //Send filled form - transaction is commited
    @Override
    public boolean sendForm() {
        Iterator iterator = this.answers.keySet().iterator();
        this.filledForm.getAnswers().clear();
        while (iterator.hasNext()) {
            Long answerId = (Long) iterator.next();
            Answer answer = this.answers.get(answerId);
            this.filledForm.getAnswers().add(answer);
        }
        //TODO here should be perfomed check if all questions were filled
        this.filledForm.setFormWasFilled(true);
        this.filledForm.setWhenFilled(new Date());
        em.merge(this.filledForm);
        //TODO: instead of setting null to this three variables, let give to the user, and oportunity to get actualised version of merged objects
        this.form = null;
        this.formSigner = null;
        this.filledForm = null;
        this.linkHash = null;
        this.transactionStarted = false;
        this.answers.clear();
        return true;
    }

    @Override
    public void cancelFormFilling() {
        System.out.println("Transaction is being cancelled!");
        if (false == this.transactionStarted) {
            return;
        }
        this.transactionStarted = false;
        this.filledForm = em.merge(this.filledForm);
        this.filledForm.getAnswers().clear();
        em.merge(this.filledForm);
        this.form = null;
        this.filledForm = null;
        this.formSigner = null;
        this.linkHash = null;
        this.answers.clear();
        return;
    }

}
